home *** CD-ROM | disk | FTP | other *** search
- /************************************************************************
- * *
- * palette.c *
- * ========= *
- * *
- * Palette optimization function library *
- * Uses spr_info generic bitmap interface from sprite.c library *
- * *
- * Version 0.35 (05-May-1993) *
- * *
- * (C) 1993 DEEJ Technology PLC *
- * *
- ************************************************************************/
-
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include "io.h"
- #include "sprite.h"
- #include "process.h"
- #include "palette.h"
-
- /* Global data with in libarary */
-
- static opt_table_entry **table;
- static int table_size;
-
- /* library internal function prototypes */
-
- static void closest_entry(int);
- static void make_closests(void);
- static int find_closest(void);
- static void eliminate(int);
- static void update_closests(int);
- static int calc_filter(hist_info*, int);
- static void filter_histogram(hist_info*, int);
-
-
- /****************************************************************************/
-
- /*
- * finds the closed and second closest entries
- * in the colour optimization table to the given entry,
- * and fills in the closest & diff fields
- */
-
- static void closest_entry(int index)
- {
- #ifdef SQR_TABLE
- static int squares[1024];
- #endif
- REGISTER int i;
- REGISTER int r, g, b, r2, g2, b2;
- REGISTER int rd, gd, bd;
- REGISTER int diff;
- int close_diff[2];
- int closest[2];
-
- #ifdef SQR_TABLE
- if(squares[2] != 4)
- {
- for(i=-512; i<512; i++)
- {
- squares[512+i] = i*i;
- }
- }
- #endif
-
- close_diff[0] = MAX_DIFF;
- close_diff[1] = MAX_DIFF;
-
- r = (table[index]->value >> 8) & 0xFF;
- g = (table[index]->value >> 16) & 0xFF;
- b = (table[index]->value >> 24) & 0xFF;
-
- for(i=0; i<table_size; i++)
- {
- /* prevent match with itself, or deleted entry */
-
- if(i!=index && table[i]!=0)
- {
- r2 = (table[i]->value >> 8) & 0xFF;
- g2 = (table[i]->value >> 16) & 0xFF;
- b2 = (table[i]->value >> 24) & 0xFF;
-
- /* difference calculation */
-
- rd = r - r2;
- gd = g - g2;
- bd = b - b2;
-
- #ifdef SQR_TABLE
- diff = squares[512+rd] * 3 +
- squares[512+gd] * 6 +
- squares[512+bd];
- #else
- diff = rd*rd * 3 +
- gd*gd * 6 +
- bd*bd;
- #endif
-
- /* check for better matches */
-
- if(diff < close_diff[0])
- {
- close_diff[1] = close_diff[0];
- close_diff[0] = diff;
- closest[1] = closest[0];
- closest[0] = i;
- }
- else
- {
- if(diff < close_diff[1])
- {
- close_diff[1] = diff;
- closest[1] = i;
- }
- }
- }
- }
-
- /* fill out table entry fields */
-
- table[index]->closest[0] = closest[0];
- table[index]->closest[1] = closest[1];
- table[index]->diff[0] = close_diff[0];
- table[index]->diff[1] = close_diff[1];
- }
-
- /*
- * for each colour in the table finds the closest
- * colour to it in the rest of the table
- *
- * Also ensure that the colours closest to the 8
- * colours at the corner of the colour cubes are
- * fixed by setting bit 0 of their value to 1
- */
-
- static void make_closests(void)
- {
- int i;
- int closest;
-
- progress_start("Differencing:");
-
- for(i=0; i<table_size; i++)
- {
- if(table[i] != 0)
- closest_entry(i);
-
- progress(i, table_size);
- }
- progress_finish();
-
- /*
- * uses extra entry in table to find closests
- * to coreners of rgb colour cube
- */
-
- if((table[table_size]=
- (opt_table_entry*)malloc(sizeof(opt_table_entry))) ==0)
- {
- fprintf(stderr,"Unable to allocate optimization table entry\n");
- exit(1);
- }
-
- for(i=0; i<8; i++)
- {
- switch(i)
- {
- case 0: table[table_size]->value = 0x00000000; break;
- case 1: table[table_size]->value = 0x0000FF00; break;
- case 2: table[table_size]->value = 0x00FF0000; break;
- case 3: table[table_size]->value = 0x00FFFF00; break;
- case 4: table[table_size]->value = 0xFF000000; break;
- case 5: table[table_size]->value = 0xFF00FF00; break;
- case 6: table[table_size]->value = 0xFFFF0000; break;
- case 7: table[table_size]->value = 0xFFFFFF00; break;
- }
- /* find closests to corner value */
-
- closest_entry(table_size);
-
- /* fix the cloesest entry */
-
- closest = table[table_size]->closest[0];
- table[closest]->value |= 1;
- }
-
- free((void*)table[table_size]);
- table[table_size] = 0;
- }
-
- /*
- * Updates the closest relationships in the table
- * after removing the target colour
- */
-
- static void update_closests(int target)
- {
- int i;
-
- for(i=0; i<table_size; i++)
- {
- if(table[i] != 0)
- {
- if(table[i]->closest[0] == target ||
- table[i]->closest[1] == target)
- {
- closest_entry(i);
- }
- }
- }
- }
-
- /*
- * Find entry that has the smallest weighted difference
- * between its two closest entries in the table.
- * Weighting is determied by RGB least squares difference and
- * the colour use count
- */
-
- static int find_closest(void)
- {
- int i;
- float d1, d2;
- float diff;
- int target;
- float closest_diff = (float)MAX_DIFF;
-
- for(i=0; i<table_size; i++)
- {
- /* check for empty or fixed entry */
-
- if((table[i]!=0) && ((table[i]->value & 1)==0))
- {
- d1 = (float)table[i]->diff[0];
- d2 = (float)table[i]->diff[1];
-
- diff = ((float)table[i]->count*d1*d2) / (d1+d2);
-
- if(diff<closest_diff && diff>0)
- {
- closest_diff = diff;
- target = i;
- }
- }
- }
- return(target);
- }
-
- /*
- * Eliminate a colour from the table and distribute
- * its usage between its two closest alternate colours
- */
-
- static void eliminate(int target)
- {
- int close1 = table[target]->closest[0];
- int close2 = table[target]->closest[1];
- int diff1 = table[target]->diff[0];
- int diff2 = table[target]->diff[1];
-
- table[close1]->count += ((table[target]->count * diff2) /
- (diff1 + diff2));
-
- table[close2]->count += ((table[target]->count * diff1) /
- (diff1 + diff2));
-
- /* remove target colour's entry */
-
- free((void*)table[target]);
- table[target] = 0;
- }
-
-
- /*
- * Calculate the optimium palette for an image
- */
-
- hist_info *build_histogram(spr_info_str *in)
- {
- static hist_info h;
- hist_entry **hash_table;
- hist_entry *entry_ptr, *last_ptr;
- int i, x, y ;
- uint rgb, r, g, b;
- int hash_val;
-
- if((hash_table = (hist_entry**)malloc(HASH_SIZE *
- sizeof(hist_entry_ptr)))==0)
- {
- fprintf(stderr,"Unable to allocate hash table\n");
- exit(1);
- }
-
- for(i=0; i<HASH_SIZE; i++)
- hash_table[i] = 0;
-
- h.colours = 0;
- h.hash_hits = 0;
- h.hash_miss = 0;
- h.hash_used = 0;
- h.hash_chain = 0;
-
- /*
- * find and count all used colours in image
- */
-
- progress_start("Histograming:");
-
- for(y=0; y<in->Y; y++)
- {
- for(x=0; x<in->X; x++)
- {
- rgb = in->read_pixel_rgb(in, x, y);
-
- r = (rgb >> 8) & 0xFF;
- g = (rgb >> 16) & 0xFF;
- b = (rgb >> 24) & 0xFF;
-
- hash_val = HASH_ALG(r,g,b);
-
- /* make hash entry if location is empty */
-
- if(hash_table[hash_val] == 0)
- {
- if((hash_table[hash_val] =
- (hist_entry*)malloc(sizeof(hist_entry))) == 0)
- {
- fprintf(stderr,
- "Hash table out of memory\n");
- exit(1);
- }
- hash_table[hash_val]->next = 0;
- hash_table[hash_val]->value = rgb;
- hash_table[hash_val]->count = 0;
-
- h.hash_used++;
- h.colours++;
- }
- entry_ptr = hash_table[hash_val];
-
- /* check for match at hash table level */
-
- if(entry_ptr->value == rgb)
- {
- /* increment usage count */
-
- entry_ptr->count++;
- h.hash_hits++;
- }
- else
- {
- /* check for match along chain from table */
-
- BOOL found=FALSE;
-
- h.hash_miss++;
-
- i = 1;
-
- last_ptr = entry_ptr;
- entry_ptr = entry_ptr->next;
-
- while(entry_ptr!=0 && found==FALSE)
- {
- if(entry_ptr->value == rgb)
- {
- found = TRUE;
- }
- else
- {
- last_ptr = entry_ptr;
- entry_ptr = entry_ptr->next;
- i++;
- }
- }
-
- /* make new entry at end of chain */
-
- if(found == FALSE)
- {
- if((entry_ptr = (hist_entry*)
- malloc(sizeof(hist_entry))) == 0)
- {
- fprintf(stderr,
- "Hash entries out of memory\n");
- exit(1);
- }
- last_ptr->next = entry_ptr;
- entry_ptr->next = 0;
- entry_ptr->value = rgb;
- entry_ptr->count = 0;
-
- if(i > h.hash_chain) h.hash_chain = i;
- h.colours++;
- }
- /* increment usage count */
-
- entry_ptr->count++;
- }
- }
- progress(y, in->Y);
- }
-
- /*
- * scan colour use table to determine filter values
- */
-
- i = 0;
- h.more1 = 0;
- h.more10 = 0;
- h.more100 = 0;
- h.more_frac = 0;
- h.fraction = (in->X * in->Y)/FILTER_FRAC;
- h.max_use = 0;
- h.avg_use = 0;
- last_ptr = 0;
-
- for(hash_val=0; hash_val<HASH_SIZE; hash_val++)
- {
- entry_ptr = hash_table[hash_val];
-
- /* fix hash table/chains to make full linked list */
-
- if(last_ptr!=0 && entry_ptr!=0)
- {
- last_ptr->next = entry_ptr;
- }
-
- while(entry_ptr != 0)
- {
- i++;
-
- if(entry_ptr->count > 1) h.more1++;
- if(entry_ptr->count > 10) h.more10++;
- if(entry_ptr->count > 100) h.more100++;
- if(entry_ptr->count > h.fraction) h.more_frac++;
- if(entry_ptr->count > h.max_use) h.max_use =
- entry_ptr->count;
- h.avg_use += entry_ptr->count;
-
- last_ptr = entry_ptr;
- entry_ptr = entry_ptr->next;
- }
- }
-
- h.avg_use /= h.colours;
-
- /*
- * can delete hash table as entries are now linked list
- * but find head of list first
- */
-
- i = 0;
- while(hash_table[i] == 0)
- {
- i++;
- }
-
- h.list_head = hash_table[i];
-
- free((void*)hash_table);
-
- progress_finish();
-
- return(&h);
- }
-
- /*
- * Calculate colour use filter threshold aim to
- * reduce table to < TABLE_MAX entries but greater
- * than the destination palette size. Can be larger
- * than TBALE_MAX so as to keep all colours used
- * more than FILTER_FRAC of total image area
- */
-
- static int calc_filter(hist_info *h, int min_cols)
- {
- int filter;
-
- filter = h->fraction;
- table_size = h->more_frac;
-
- if(table_size<TABLE_MAX && h->more100>table_size && h->more100<TABLE_MAX)
- {
- filter = 100;
- table_size = h->more100;
- }
- if(table_size<TABLE_MAX && h->more10>table_size && h->more10<TABLE_MAX)
- {
- filter = 10;
- table_size = h->more10;
- }
- if(table_size<TABLE_MAX && h->more1>table_size && h->more1<TABLE_MAX)
- {
- filter = 1;
- table_size = h->more1;
- }
- if(table_size<TABLE_MAX && h->colours<TABLE_MAX)
- {
- filter = 0;
- table_size = h->colours;
- }
-
- return(filter);
- }
-
- /*
- * Make optimization table by filtering hash_table
- * hash_table list entries can be removed after copying
- * 'table' and 'table_size' are globals
- */
-
- static void filter_histogram(hist_info *h, int filter)
- {
- hist_entry *entry_ptr;
- hist_entry *last_ptr;
- int i;
-
- /* allocate table size + 1 for temporary calculations */
-
- if((table = (opt_table_entry **)malloc((table_size+1) *
- sizeof(opt_table_entry_ptr)))==0)
- {
- fprintf(stderr,"Unable to allocate optimization table\n");
- exit(1);
- }
-
- i = 0;
- entry_ptr = h->list_head;
-
- while(entry_ptr != 0)
- {
- if(entry_ptr->count > filter)
- {
- if((table[i]=(opt_table_entry*)malloc(
- sizeof(opt_table_entry))) ==0)
- {
- fprintf(stderr,"Unable to allocate optimization table entry\n");
- exit(1);
- }
- table[i]->value = entry_ptr->value;
- table[i]->count = entry_ptr->count;
- table[i]->closest[0] = 0;
- table[i]->closest[1] = 0;
- table[i]->diff[0] = MAX_DIFF;
- table[i]->diff[1] = MAX_DIFF;
- i++;
- }
- last_ptr = entry_ptr;
- entry_ptr = entry_ptr->next;
- free((void*)last_ptr);
- }
- }
-
- /*
- * Calculate the optimium palette for an image
- *
- * If source image has more colours than destination
- * colours are calculated on a use/RGB difference alogorithm
- * to produce the smallest error in the output image.
- * If the destination has more colurs than the source, its
- * palette is set to contain only the colours used in the
- * source image, rather than its whole palette.
- * The output palette must have more than 8 colours.
- */
-
- void optimize_palette(spr_info_str *in, spr_info_str *out)
- {
- hist_info *hist;
- int filter;
- int colours;
- int target;
- int i;
-
- /* check for non paletted / too small palette output image */
-
- if(out->bpp>8 || out->bpp<4) return;
-
- /* build histogram of colour use counts */
-
- hist = build_histogram(in);
-
- #ifdef HASH_STAT
- fprintf(stderr,"Hash used : %-7d %3d%%\n",hist->hash_used,
- hist->hash_used*
- 100/HASH_SIZE);
- fprintf(stderr,"Hash hits : %-7d %3d%%\n",hist->hash_hits,
- hist->hash_hits*
- 100/(in->X*in->Y));
- fprintf(stderr,"Hash misses : %-7d %3d%%\n",hist->hash_miss,
- hist->hash_miss*
- 100/(in->X*in->Y));
- fprintf(stderr,"Hash chain : %-7d\n", hist->hash_chain);
- #endif
- #ifdef COLOUR_STAT
- fprintf(stderr,"Colours used: %d\n",hist->colours);
- fprintf(stderr,"used > 1 : %d\n",hist->more1);
- fprintf(stderr,"used > 10 : %d\n",hist->more10);
- fprintf(stderr,"used > 100 : %d\n",hist->more100);
- fprintf(stderr,"used > %.2f%%: %-5d (%d pixels)\n",
- 100.0/(float)FILTER_FRAC,
- hist->more_frac,
- hist->fraction);
- #endif
-
- filter = calc_filter(hist, out->cols);
- filter_histogram(hist, filter);
-
- #ifdef COLOUR_STAT
- fprintf(stderr,"Filer chosen: %d\n",filter);
- fprintf(stderr,"Table size : %d\n",table_size);
- #endif
-
- /*
- * for each colour in the table find the
- * closest color in the rest of the table
- */
-
- make_closests();
-
- progress_start("Optimizing :");
-
- colours = table_size;
-
- while(colours > out->cols)
- {
- target = find_closest();
- eliminate(target);
- update_closests(target);
- colours--;
- progress(table_size - colours, table_size - out->cols);
- }
-
- progress_finish();
-
- /*
- * copy optimization table to destination palette
- * and remove table storage
- */
-
- colours = 0;
-
- for(i=0; i<table_size; i++)
- {
- if(table[i] != 0)
- {
- out->palette[colours++] = table[i]->value;
-
- free((void*)table[i]);
- }
- }
- free((void*)table);
- }
-
-